﻿using System.Diagnostics;
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else
using System.Windows;
#if SILVERLIGHT
using System.Windows.Controls;
#endif
#endif

namespace FreshMeat.Xaml
{
   /// <summary>
   /// Sets the initial focus to a "target" element whenever the parent element (to which this
   /// behavior is attached) is loaded.
   /// </summary>
   /// <remarks>
   /// Usage:
   ///    InitialFocusBehavior.Target="{Binding ElementName=MyButton}"
   /// </remarks>
   public class InitialFocusBehavior : AttachedBehavior<InitialFocusBehavior, FrameworkElement>
   {
      static readonly BooleanSwitch trace = new BooleanSwitch("Focus", "Focus");

      /// <summary>
      /// The element which will receive the focus.
      /// </summary>
#if !SILVERLIGHT && !NETFX_CORE
      public readonly static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached(
         "Target",
         typeof(UIElement),
         typeof(InitialFocusBehavior),
         // Cast of null is critical -- WPF doesn't work without it!
         new FrameworkPropertyMetadata((UIElement)null, (d, e) => GetInstance(d)));

      public static UIElement GetTarget(DependencyObject d)
      {
         return (UIElement)d.GetValue(TargetProperty);
      }

      public static void SetTarget(DependencyObject d, UIElement element)
      {
         d.SetValue(TargetProperty, element);
      }
#else
      public readonly static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached(
         "Target",
         typeof(Control),
         typeof(InitialFocusBehavior),
         new PropertyMetadata(null, (d, e) => GetInstance(d)));

      public static Control GetTarget(DependencyObject d)
      {
         return (Control)d.GetValue(TargetProperty);
      }

      public static void SetTarget(DependencyObject d, Control element)
      {
         d.SetValue(TargetProperty, element);
      }
#endif

      protected override void OnAttached()
      {
         var associatedObject = TryGetAssociatedObject();
         associatedObject.Loaded += OnAssociatedObjectLoaded;

         // In Silverlight, the loaded event occurs BEFORE the binding is performed so
         // by the time this method is called, the loaded event has already occurred.
         // Therefore, we must do the focus work at this point.
         //
         // So why hook the loaded event at all in Silverlight? Because in some cases,
         // such as for the contents of a TabItem, the loaded/unloaded events are fired 
         // by the TabControl as a way of telling the contents that they are being 
         // activated/deactivated as the selected tab changes.  We do want to receive 
         // these "simulated" loaded events since the binding does NOT get redone when 
         // the selected tab changes, so we do need to hook the loaded event.
         //
         // In WPF, we do the focus work here in addition to when the loaded event fires.
         // This is because in some cases, such as when a new Window is created, the
         // loaded event has already occurred by the time we get here.  But for all other
         // cases, we'll end up doing the focus work twice -- once here and once when 
         // the loaded event fires.  It's inefficient and could possibly be solved by
         // setting a flag and avoiding doing it twice, but it doesn't seem worth it.
         DoFocus(associatedObject);
      }

      protected override void OnDetaching()
      {
         var associatedObject = TryGetAssociatedObject();
         if (associatedObject == null)
            return;

         associatedObject.Loaded -= OnAssociatedObjectLoaded;
      }

      void OnAssociatedObjectLoaded(object sender, RoutedEventArgs routedEventArgs)
      {
         var associatedObject = TryGetAssociatedObject();
         if (associatedObject == null)
            return;

         DoFocus(associatedObject);
      }

      void DoFocus(FrameworkElement associatedObject)
      {
         var target = GetTarget(associatedObject);
         if (target == null)
            return;

#if !SILVERLIGHT && !NETFX_CORE
         var focusScope = FocusHelper.FindParentFocusScope(associatedObject);
         if (focusScope == null || !FocusHelper.IsFocusable(focusScope))
            return;
#endif

         if (!FocusHelper.IsFocusable(target))
            return;

         if (trace.Enabled)
            Trace.TraceInformation("InitialFocusBehavior is setting focus to target");
         FocusHelper.SetFocus(target);
      }
   }
}
